let pi = 3.14159265
let is_clockwise (nx,ny,nz) =
                (ny <= 0.)

(*drawoObj affiche le tableau de faces et le tableau de normales*)
let drawObj selmod fArray nArray cArray fline=
  let sel = !selmod
  in
  let getDraw (x,y,z) =
    if sel then
      begin
        if fline (x,z) then
          1.
        else
          0.2
      end
    else
      1.
  in

  let draw (n1,n2,n3) (c1,c2,c3) (p1,p2,p3) =
    GlDraw.begins `triangles;
    GlDraw.color c1 ~alpha:(getDraw p1);
    GlDraw.normal3 n1;
    GlDraw.vertex3 p1;
    GlDraw.color c2 ~alpha:(getDraw p2);
    GlDraw.normal3 n2;
    GlDraw.vertex3 p2;
    GlDraw.color c3 ~alpha:(getDraw p3);
    GlDraw.normal3 n3;
    GlDraw.vertex3 p3;
    GlDraw.ends ();
  in

    begin
      for i = 0 to ((Array.length fArray) -1) do
        let (v1,v2,v3) as v = fArray.(i) in
        let n = nArray.(i) in
        let c = cArray.(i) in
          draw n c v;
      done;
    end



(* Main display function. *)
let display selmod light_state (cam:Camera.camera)
      w h fArray nArray cArray fline () =
  if cam#get_moving then
    cam#moveToUpdate;
  GlClear.clear [`color; `depth];
  Gl.enable `depth_test;
  GlMat.mode `projection;
  GlMat.load_identity ();

  let aspect = float !w /. float !h in
    GluMat.perspective ~fovy:90.0 ~aspect:aspect ~z:(0.1, 10000.);

    GluMat.look_at ~eye:cam#get_pos
			~center:cam#get_dir ~up:cam#get_up;
    GlMat.mode `modelview;
    GlMat.load_identity ();
    drawObj selmod !fArray !nArray !cArray fline;

    GlMat.push ();
    Gl.enable `lighting;
    Gl.enable `light0;

    if !light_state then
         GlMat.translate3 (cam#get_pos)
    else
         GlMat.translate3 (200.,2000.,200.);

    Gl.enable `color_material;
    GlLight.material `both (`shininess 100.);
    GlLight.light ~num:0 (`position (0.,0.0,0.,20.0));
    (*GlLight.color_material `both `specular;*)
    GlLight.color_material `both `ambient_and_diffuse;
    GlMat.pop ();
    Gl.disable `depth_test;
    Gl.flush ()



(*reshape est appelle au debut de la boucle Glut et permet d'adapter la
taille de la fenetre au rendu *)
let reshape w h (cam:Camera.camera) newWidth newHeight=
  w := newWidth;
  h := newHeight;

  Gl.enable `blend;
  GlFunc.blend_func `src_alpha `one_minus_src_alpha;
  Gl.enable `alpha_test;
  GlFunc.alpha_func `greater ~ref:0.;

  cam#set_mid (!w,!h);
  GlDraw.viewport ~x:0 ~y:0 ~w:!w ~h:!h;
  GlMat.mode `projection;
  GlMat.load_identity ();
  GlMat.mode `modelview;
  GlMat.load_identity ()


(* key_callback gere les evene;emts speciaux du clavier comme
l'appuie sur les fleche directionnelles ou les F1-F12, elle prend
la direction et la position de la camera en parametre*)
(* keyboard gere les evenements du clavier elle prend le boolen fillaire
en parametre, la position de la camera et la direction de la camera *)
let keyboard light_state wire (cam:Camera.camera) key =
	match key with
	  	(*match keys*)
		|52 -> (wire := not !wire ;
			if !wire then
                        	GlDraw.polygon_mode `both `line
			else
				GlDraw.polygon_mode `both `fill)
		|53 -> light_state := not !light_state
		|116 -> cam#keydown
		|111 -> cam#keyup
                |114 -> cam#keyleft
                |113 -> cam#keyright
		|_ -> ()

(*mouse permet de recuperer et de mettre a jour le position de la souris
et la direction de la camera, on lappelle avec les reference de la
position du curseur, les demies largeur et longueur de l'ecran et
le vecteur direction*)

let mouse (cam:Camera.camera) ~x ~y =
	cam#mouse x y

let mousePassive (cam:Camera.camera) ~x ~y =
        cam#mousePassive x y

(* getNarray prend en parametre un tableau de triplets de triplets
de flotants corresondant aux points des triangles et renvoie le tableau
des normale de ces faces sous forme *)
let getNarray fArray =
        let nArray = Array.make (Array.length fArray) (0.,0.,0.) in
        for i = 0 to (Array.length fArray) - 1 do
                let (v1,v2,v3) = fArray.(i) in
                let a = Geometry.fromC3 v3 in
                let b = Geometry.fromC3 v2 in
                let c = Geometry.fromC3 v1 in
                let n =  Geometry.cross (Geometry.diff a b)
                                       (Geometry.diff a c) in
		if (is_clockwise (Geometry.toC3 n)) then
		    fArray.(i) <- (v3,v2,v1);

                Geometry.normalize n;
                nArray.(i) <- Geometry.toC3 n;
        done;
        nArray

(* execute est appelee de l'exterieur avec une chaine de caractere
et permet l'ouverture de la fenetre et l'affichage du fichier obj
dont le nom est la chaine de caractere *)

let getSoftNormals nArray nbvert faces =
        let v = Array.make nbvert [] in
        let nbFaces = (Array.length faces) - 1 in
        for i = 0 to nbFaces do
                let (v1,v2,v3) = ((List.nth faces.(i) 0) - 1,
                                (List.nth faces.(i) 1) - 1,
                                (List.nth faces.(i) 2) - 1) in
                v.(v1) <- i::v.(v1);
                v.(v2) <- i::v.(v2);
                v.(v3) <- i::v.(v3);
        done;
        let vnul = (0.,0.,0.) in
        let n = Array.make (nbvert) vnul in
        let rec sum = function
                        [] -> (0.,0.,0.)
                        |f::l ->   let (x,y,z) = nArray.(f) in
                                   let (x1,y1,z1) = sum l in
                                        (x +. x1,y +. y1, z +. z1)
        in
        let div (x,y,z) s = (x/.s ,y/.s,z/.s) in
        for i = 0 to nbvert - 1 do
                let l = float_of_int (List.length v.(i)) in
                if not (l = 0.) then
                         n.(i) <- div (sum (v.(i))) l
                else
                        n.(i) <- (0.,0.,0.)
        done;
        ObjLoader.mergeVF (n,faces)


let getCarray fArray charte =
	let cArray = Array.make (Array.length fArray)
                ((0.,0.,0.),(0.,0.,0.),(0.,0.,0.)) in
	for i = 0 to (Array.length fArray) - 1 do
		let ((x1,_,z1),(x2,_,z2),(x3,_,z3)) = fArray.(i) in
		let (c1,c2,c3) = (charte (x1,z1), charte (x2,z2), charte(x3,z3)) in
		cArray.(i) <- (c1,c2,c3);
        done;
	cArray


let init () =
	()


let get_pos (cam:Camera.camera) () =
	cam#get_pos

let set_pos (cam:Camera.camera) pos =
	cam#set_pos pos
let get_dir (cam:Camera.camera) () =
	cam#get_dir_nopos
let set_dir (cam:Camera.camera) pos =
	cam#set_dir pos
let set_dir_and_pos (cam:Camera.camera) norotate pos dir =
  cam#moveToInit norotate pos dir

let set_select_mode sel mode =
  sel := mode

let set_wire wire is_wire=
  wire := is_wire;
  if !wire then
    GlDraw.polygon_mode `both `line
  else
    GlDraw.polygon_mode `both `fill


let reload fArray nArray cArray charte objPath=
  let (fArrayTemp,vertices,faces) = (ObjLoader.readObj (objPath)) in
  fArray := fArrayTemp;
  let nArrayTemp = (getNarray !fArray) in
  nArray := (getSoftNormals nArrayTemp
                                (Array.length vertices) faces );
  cArray := (getCarray !fArray charte)

let execute width height objPath fline charte =
  let wire = ref false in
  let light_state = ref false in
  let select = ref false in
  let (fArrayTemp,vertices,faces) = (ObjLoader.readObj (objPath)) in
  let fArray = ref fArrayTemp in
  let nArrayTemp = (getNarray !fArray) in
  let nArray = ref (getSoftNormals nArrayTemp
                                (Array.length vertices) faces )  in
  let cArray = ref (getCarray !fArray charte) in

  let (w,h) = (ref width, ref height) in
  let cam = new Camera.camera in
		set_dir cam (0.,pi,1.0);
  (display select light_state cam w h fArray nArray cArray fline,
  reshape w h cam, init, keyboard light_state wire cam,mouse cam,
   mousePassive cam,set_pos cam, get_pos cam,set_dir cam, get_dir cam ,
   set_wire wire,reload fArray nArray cArray charte ,set_dir_and_pos cam,
   set_select_mode select, (fun () -> cam#get_moving),cam#set_speed)
